// Copyright (c) Aptos Foundation
// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE

use crate::transaction::authenticator::AnyPublicKey;
use anyhow::{anyhow, Result};
use aptos_crypto::{
    hash::CryptoHash, secp256r1_ecdsa, signing_message, CryptoMaterialError, HashValue, Signature,
};
use passkey_types::{crypto::sha256, webauthn::CollectedClientData, Bytes};
use serde::{Deserialize, Serialize};

pub const MAX_WEBAUTHN_SIGNATURE_BYTES: usize = 1024;

/// Returns the binary concatenation of
/// 1. [`authenticator_data_bytes`](PartialAuthenticatorAssertionResponse) and
/// 2. SHA-256 hash of [`client_data_json`](PartialAuthenticatorAssertionResponse),
///
/// See <https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion>
fn generate_verification_data(authenticator_data_bytes: &[u8], client_data_json: &[u8]) -> Vec<u8> {
    // Let hash be the result of computing a hash over the clientData using SHA-256.
    let client_data_json_hash = sha256(client_data_json);
    // Binary concatenation of authData and hash.
    // Note: This is compatible with signatures generated by FIDO U2F
    // authenticators. See §6.1.2 FIDO U2F Signature Format Compatibility
    // See <https://www.w3.org/TR/webauthn-3/#sctn-fido-u2f-sig-format-compat>
    [authenticator_data_bytes, &client_data_json_hash]
        .concat()
        .to_vec()
}

/// This checks that the SHA3-256 of the [`signing_message(message)`](signing_message)
/// is equal to the actual `challenge` from `client_data_json` in `PartialAuthenticatorAssertionResponse`
fn verify_expected_challenge_from_message_matches_actual<T: Serialize + CryptoHash>(
    message: &T,
    actual_challenge: &[u8],
) -> std::result::Result<(), CryptoMaterialError> {
    // Generate signing_message, which is the BCS encoded bytes of message, prefixed with a hash
    let signing_message_bytes = signing_message(message)?;
    // Expected challenge is SHA3-256 digest of RawTransaction bytes
    let expected_challenge = HashValue::sha3_256_of(signing_message_bytes.as_slice());

    expected_challenge
        .to_vec()
        .as_slice()
        .eq(actual_challenge)
        .then_some(())
        .ok_or(CryptoMaterialError::ValidationError)
}

#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum AssertionSignature {
    Secp256r1Ecdsa {
        signature: secp256r1_ecdsa::Signature,
    },
}

/// Custom arbitrary implementation for fuzzing
/// as the `secp256r1_ecdsa::Signature` type is an external dependency
/// p256::ecdsa::Signature
#[cfg(feature = "fuzzing")]
impl<'a> arbitrary::Arbitrary<'a> for AssertionSignature {
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
        // Generate a fixed-length byte array for the signature
        let bytes: [u8; aptos_crypto::secp256r1_ecdsa::Signature::LENGTH] = u.arbitrary()?;

        // Create a signature without validating it
        let signature = aptos_crypto::secp256r1_ecdsa::Signature::from_bytes_unchecked(&bytes)
            .map_err(|_| arbitrary::Error::IncorrectFormat)?;

        Ok(AssertionSignature::Secp256r1Ecdsa { signature })
    }
}

/// `PartialAuthenticatorAssertionResponse` includes a subset of the fields returned from
/// an [`AuthenticatorAssertionResponse`](passkey_types::webauthn::AuthenticatorAssertionResponse)
///
/// See <https://www.w3.org/TR/webauthn-3/#authenticatorassertionresponse>
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))]
pub struct PartialAuthenticatorAssertionResponse {
    /// This attribute contains the raw signature returned from the authenticator.
    /// NOTE: Many signatures returned from WebAuthn assertions are not raw signatures.
    /// As an example, Secp256r1 ECDSA signatures are encoded as an [ASN.1 DER Ecdsa-Sig_value](https://www.w3.org/TR/webauthn-3/#sctn-signature-attestation-types)
    /// If the signature is encoded, the client is expected to convert the encoded signature
    /// into a raw signature before including it in the transaction
    signature: AssertionSignature,
    /// This attribute contains the authenticator data returned by the authenticator.
    /// See [`AuthenticatorData`](passkey_types::ctap2::AuthenticatorData).
    #[serde(with = "serde_bytes")]
    authenticator_data: Vec<u8>,
    /// This attribute contains the JSON byte serialization of [`CollectedClientData`](CollectedClientData) passed to the
    /// authenticator by the client in order to generate this credential. The exact JSON serialization
    /// MUST be preserved, as the hash of the serialized client data has been computed over it.
    #[serde(with = "serde_bytes")]
    client_data_json: Vec<u8>,
}

impl PartialAuthenticatorAssertionResponse {
    pub fn new(
        assertion_signature: AssertionSignature,
        authenticator_data: Vec<u8>,
        client_data_json: Vec<u8>,
    ) -> Self {
        PartialAuthenticatorAssertionResponse {
            signature: assertion_signature,
            authenticator_data,
            client_data_json,
        }
    }

    /// Uses BCS serialization
    pub fn to_bytes(&self) -> Vec<u8> {
        bcs::to_bytes(&self).expect("Only unhandleable errors happen here.")
    }

    pub fn signature(&self) -> &AssertionSignature {
        &self.signature
    }

    pub fn signature_bytes(&self) -> Vec<u8> {
        bcs::to_bytes(&self.signature).expect("Only unhandleable errors happen here.")
    }

    /// In our adaptation of WebAuthn, the `challenge` provided to `authenticatorGetAssertion`
    /// is the SHA3-256 digest of the `RawTransaction`.
    ///
    /// This function should do the following:
    /// 1. Verify `actual_challenge` and expected challenge from message are equal
    /// 2. Construct `verification_data` as the binary concatenation of
    ///    authenticator_data and SHA-256(client_data_json)
    /// 3. Signature verification
    ///
    /// See WebAuthn §6.3.3 `authenticatorGetAssertion` for more info
    pub fn verify<T: Serialize + CryptoHash>(
        &self,
        message: &T,
        public_key: &AnyPublicKey,
    ) -> Result<()> {
        let collected_client_data: CollectedClientData =
            serde_json::from_slice(self.client_data_json.as_slice())?;
        let challenge_bytes = Bytes::try_from(collected_client_data.challenge.as_str())
            .map_err(|e| anyhow!("Failed to decode challenge bytes {:?}", e))?;

        // Check if expected challenge and actual challenge match. If there's no match, throw error
        verify_expected_challenge_from_message_matches_actual(message, challenge_bytes.as_slice())?;

        // Generates binary concatenation of authenticator_data and hash(client_data_json)
        let verification_data = generate_verification_data(
            self.authenticator_data.as_slice(),
            self.client_data_json.as_slice(),
        );

        // Note: We must call verify_arbitrary_msg instead of verify here. We do NOT want to
        // use verify because it BCS serializes and prefixes the message with a hash
        // via the signing_message function invocation
        match (&public_key, &self.signature) {
            (
                AnyPublicKey::Secp256r1Ecdsa { public_key },
                AssertionSignature::Secp256r1Ecdsa { signature },
            ) => signature.verify_arbitrary_msg(&verification_data, public_key),
            _ => Err(anyhow!(
                "WebAuthn verification failure, invalid key, signature pairing"
            )),
        }
    }

    /// In our adaptation of WebAuthn, the `challenge` provided to `authenticatorGetAssertion`
    /// is the SHA3-256 digest of the `RawTransaction`.
    ///
    /// This function should do the following:
    /// 1. Verify `actual_challenge` and expected challenge from message are equal
    /// 2. Construct `verification_data` as the binary concatenation of
    ///    authenticator_data and SHA-256(client_data_json)
    /// 3. Signature verification
    ///
    /// See WebAuthn §6.3.3 `authenticatorGetAssertion` for more info
    pub fn verify_arbitrary_msg(&self, message: &[u8], public_key: &AnyPublicKey) -> Result<()> {
        let collected_client_data: CollectedClientData =
            serde_json::from_slice(self.client_data_json.as_slice())?;
        let challenge_bytes = Bytes::try_from(collected_client_data.challenge.as_str())
            .map_err(|e| anyhow!("Failed to decode challenge bytes {:?}", e))?;

        // Check if expected challenge and actual challenge match. If there's no match, throw error
        challenge_bytes
            .as_slice()
            .eq(message)
            .then_some(())
            .ok_or(CryptoMaterialError::ValidationError)?;

        // Generates binary concatenation of authenticator_data and hash(client_data_json)
        let verification_data = generate_verification_data(
            self.authenticator_data.as_slice(),
            self.client_data_json.as_slice(),
        );

        // Note: We must call verify_arbitrary_msg instead of verify here. We do NOT want to
        // use verify because it BCS serializes and prefixes the message with a hash
        // via the signing_message function invocation
        match (&public_key, &self.signature) {
            (
                AnyPublicKey::Secp256r1Ecdsa { public_key },
                AssertionSignature::Secp256r1Ecdsa { signature },
            ) => signature.verify_arbitrary_msg(&verification_data, public_key),
            _ => Err(anyhow!(
                "WebAuthn verification failure, invalid key, signature pairing"
            )),
        }
    }
}

impl TryFrom<&[u8]> for PartialAuthenticatorAssertionResponse {
    type Error = CryptoMaterialError;

    /// Uses BCS serialization
    fn try_from(
        bytes: &[u8],
    ) -> core::result::Result<PartialAuthenticatorAssertionResponse, CryptoMaterialError> {
        bcs::from_bytes::<PartialAuthenticatorAssertionResponse>(bytes)
            .map_err(|_e| CryptoMaterialError::DeserializationError)
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        test_helpers::transaction_test_helpers::get_test_raw_transaction,
        transaction::{
            authenticator::{AnyPublicKey, AuthenticationKey},
            webauthn::{AssertionSignature, PartialAuthenticatorAssertionResponse},
            RawTransaction,
        },
    };
    use anyhow::anyhow;
    use aptos_crypto::{
        secp256r1_ecdsa,
        secp256r1_ecdsa::{PrivateKey, PublicKey, Signature},
        signing_message, HashValue, PrivateKey as PrivateKeyTrait, Uniform,
        ValidCryptoMaterialStringExt,
    };
    use coset::CoseKey;
    use move_core_types::account_address::AccountAddress;
    use p256::pkcs8::DecodePublicKey;
    use passkey_authenticator::{
        public_key_der_from_cose_key, Authenticator, UserValidationMethod,
    };
    use passkey_client::Client;
    use passkey_types::{
        ctap2,
        ctap2::Aaguid,
        rand::random_vec,
        webauthn::{
            AttestationConveyancePreference, AuthenticatedPublicKeyCredential, CollectedClientData,
            CreatedPublicKeyCredential, CredentialCreationOptions, CredentialRequestOptions,
            PublicKeyCredentialCreationOptions, PublicKeyCredentialParameters,
            PublicKeyCredentialRequestOptions, PublicKeyCredentialRpEntity,
            PublicKeyCredentialType, PublicKeyCredentialUserEntity, UserVerificationRequirement,
        },
        Bytes, Passkey,
    };
    use rand::{rngs::StdRng, SeedableRng};
    use url::Url;

    type Challenge = Vec<u8>;
    type RawTxnSigningMessage = Vec<u8>;

    struct MyUserValidationMethod {}
    #[async_trait::async_trait]
    impl UserValidationMethod for MyUserValidationMethod {
        async fn check_user_verification(&self) -> bool {
            true
        }

        async fn check_user_presence(&self) -> bool {
            true
        }

        fn is_presence_enabled(&self) -> bool {
            true
        }

        fn is_verification_enabled(&self) -> Option<bool> {
            Some(true)
        }
    }

    /// Contains the public key of the Passkey credential
    /// This is included in a passkey registration response and derived from a real
    /// [AuthenticatorAttestationResponse](passkey_types::webauthn::AuthenticatorAttestationResponse)
    /// using `passkeys-ts`
    ///
    /// See <https://github.com/aptos-labs/passkeys-ts>
    static ATTESTATION_OBJECT: &str = "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAAAAAAAAAAAAAAAAAAAAAAAAFG_FKuGMNWGiFATwhbpPPcm-wkFLpQECAyYgASFYIMXShkyn1SgV8l9FKzTdChTDgnn9HB2TuXHLf1GA_VuGIlggRj8IW2yK6AcefsrfbM_6WIqK5CTVpkhtT8WFRvUAfcg";

    /// Contains the public key of a Secure Payment Confirmation (SPC) enabled Passkey credential
    /// This is included in a passkey registration response and derived from a real
    /// [AuthenticatorAttestationResponse](passkey_types::webauthn::AuthenticatorAttestationResponse)
    /// using `passkeys-ts`
    ///
    /// See <https://github.com/aptos-labs/passkeys-ts>
    static SPC_ATTESTATION_OBJECT: &str = "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAK3OAAI1vMYKZIsLJfHwVQMAIIwkb4vzhrcbFEOzI6_J233DJiKugvfuflXjcIXQunjupQECAyYgASFYIB1yTmeAb93Xv2GaYO_SV3cDKqJeRX5jYqqgijy_9d0bIlggur6NDT5HhEu0VPlzpevZt2lOGh0qiNXfFytsqCLPZ8E";

    /// BCS encoded coin transfer raw transaction
    static RAW_TXN_BCS_BYTES: &[u8] = &[
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 13, 97, 112, 116, 111, 115, 95, 97, 99, 99, 111,
        117, 110, 116, 14, 116, 114, 97, 110, 115, 102, 101, 114, 95, 99, 111, 105, 110, 115, 1, 7,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 1, 10, 97, 112, 116, 111, 115, 95, 99, 111, 105, 110, 9, 65, 112, 116, 111, 115, 67,
        111, 105, 110, 0, 2, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 8, 232, 3, 0, 0, 0, 0, 0, 0, 232, 3, 0, 0, 0, 0, 0, 0, 100,
        0, 0, 0, 0, 0, 0, 0, 128, 116, 23, 188, 190, 0, 0, 0, 89,
    ];

    /// Passkey Authenticator Data
    /// This is generated from a real [AuthenticatorAssertionResponse](passkey_types::webauthn::AuthenticatorAssertionResponse)
    /// using `passkeys-ts`
    ///
    /// See <https://github.com/aptos-labs/passkeys-ts>
    static AUTHENTICATOR_DATA: &[u8] = &[
        73, 150, 13, 229, 136, 14, 140, 104, 116, 52, 23, 15, 100, 118, 96, 91, 143, 228, 174, 185,
        162, 134, 50, 199, 153, 92, 243, 186, 131, 29, 151, 99, 29, 0, 0, 0, 0,
    ];

    /// Client Data JSON, using the `SHA3-256(signed_message(raw_transaction))` as the `challenge`
    /// This is generated from a real [AuthenticatorAssertionResponse](passkey_types::webauthn::AuthenticatorAssertionResponse)
    /// using `passkeys-ts`
    ///
    /// See <https://github.com/aptos-labs/passkeys-ts>
    static CLIENT_DATA_JSON: &[u8] = &[
        123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110, 46, 103,
        101, 116, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110, 103, 101, 34, 58, 34, 101, 85, 102,
        49, 97, 88, 119, 100, 116, 72, 75, 110, 73, 89, 85, 88, 107, 84, 103, 72, 120, 109, 87,
        116, 89, 81, 95, 85, 48, 99, 51, 79, 56, 76, 100, 109, 120, 51, 80, 84, 65, 95, 103, 34,
        44, 34, 111, 114, 105, 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 58, 47, 47, 108, 111,
        99, 97, 108, 104, 111, 115, 116, 58, 53, 49, 55, 51, 34, 44, 34, 99, 114, 111, 115, 115,
        79, 114, 105, 103, 105, 110, 34, 58, 102, 97, 108, 115, 101, 125,
    ];

    /// Compact Secp256r1 ECDSA Signature computed over the binary concatenation of
    /// AUTHENTICATOR_DATA and SHA-256(CLIENT_DATA_JSON)
    ///
    /// This is generated from a real [AuthenticatorAssertionResponse](passkey_types::webauthn::AuthenticatorAssertionResponse)
    /// using `passkeys-ts`
    ///
    /// See <https://github.com/aptos-labs/passkeys-ts>
    static SIGNATURE: &[u8] = &[
        113, 168, 216, 132, 231, 240, 12, 39, 184, 16, 246, 230, 166, 142, 70, 117, 131, 2, 3, 155,
        44, 87, 236, 192, 192, 28, 110, 2, 33, 143, 17, 200, 62, 221, 102, 227, 147, 24, 126, 96,
        10, 168, 199, 184, 85, 11, 3, 212, 24, 148, 12, 118, 60, 116, 123, 117, 228, 159, 139, 235,
        130, 6, 114, 60,
    ];

    /// The function `ccd_to_string` is used in
    /// [`collected_client_data_to_json_bytes`](collected_client_data_to_json_bytes)
    /// and is defined as:
    /// 1. Let encoded be an empty byte string.
    /// 2. Append 0x22 (") to encoded. -> 0x22 is the hexadecimal for a double quote (")
    /// 3. Invoke ToString on the given object to convert to a string.
    /// 4. For each code point in the resulting string, if the code point:
    ///
    ///     -> is in the set {U+0020, U+0021, U+0023–U+005B, U+005D–U+10FFFF}
    ///             Append the UTF-8 encoding of that code point to encoded.
    ///
    ///     -> is U+0022
    ///             Append 0x5c22 (\") to encoded.
    ///
    ///     -> is U+005C
    ///             Append 0x5c5c (\\) to encoded.
    ///
    ///     -> otherwise
    ///             Append 0x5c75 (\u) to encoded, followed by four, lower-case hex digits that,
    ///             when interpreted as a base-16 number, represent that code point.
    ///
    /// 5. Append 0x22 (") to encoded.
    /// 6. The result of this function is the value of encoded.
    fn ccd_to_string(input: &str) -> Vec<u8> {
        let mut encoded = Vec::new();

        // Append 0x22 (")
        encoded.push(0x22);

        for code_point in input.chars() {
            match code_point {
                '\u{0020}' | '\u{0021}' | '\u{0023}'..='\u{005B}' | '\u{005D}'..='\u{10FFFF}' => {
                    // Append the UTF-8 encoding of the code point
                    let utf8_bytes = code_point.to_string().into_bytes();
                    encoded.extend_from_slice(&utf8_bytes);
                },
                '\u{0022}' => {
                    // Append 0x5c22 (\")
                    encoded.push(0x5C);
                    encoded.push(0x22);
                },
                '\u{005C}' => {
                    // Append 0x5c5c (\\)
                    encoded.push(0x5C);
                    encoded.push(0x5C);
                },
                _ => {
                    // Append 0x5c75 (\u) followed by four lower-case hex digits
                    encoded.push(0x5C);
                    encoded.push(0x75);
                    let hex_digits = format!("{:04x}", code_point as u32);
                    for hex_byte in hex_digits.bytes() {
                        encoded.push(hex_byte);
                    }
                },
            }
        }

        // Append 0x22 (")
        encoded.push(0x22);

        encoded
    }

    /// This is the custom serialization of [`CollectedClientData`](CollectedClientData)
    /// that is performed by the device authenticator, referenced in the WebAuthn spec, under
    /// Section §5.8.1.1 Serialization.
    ///
    /// This is helpful for ensuring that the serialization of [`CollectedClientData`](CollectedClientData)
    /// is identical to the device authenticator's output for clientDataJSON in client assertions.
    ///
    /// The serialization of the [`CollectedClientData`](CollectedClientData)
    /// is a subset of the algorithm for JSON-serializing
    /// to bytes. I.e. it produces a valid JSON encoding of the `CollectedClientData` but also provides
    /// additional structure that may be exploited by verifiers to avoid integrating a full JSON parser.
    /// While verifiers are recommended to perform standard JSON parsing, they may use the more
    /// limited algorithm below in contexts where a full JSON parser is too large. This verification
    /// algorithm requires only base64url encoding, appending of bytestrings (which could be
    /// implemented by writing into a fixed template), and three conditional checks (assuming that
    /// inputs are known not to need escaping).
    ///
    /// The serialization algorithm works by appending successive byte strings to an, initially empty,
    /// partial result until the complete result is obtained.
    ///
    /// 1. Let result be an empty byte string.
    /// 2. Append 0x7b2274797065223a ({"type":) to result.
    /// 3. Append CCDToString(type) to result.
    /// 4. Append 0x2c226368616c6c656e6765223a (,"challenge":) to result.
    /// 5. Append CCDToString(challenge) to result.
    /// 6. Append 0x2c226f726967696e223a (,"origin":) to result.
    /// 7. Append CCDToString(origin) to result.
    /// 8. Append 0x2c2263726f73734f726967696e223a (,"crossOrigin":) to result.
    /// 9. If crossOrigin is not present, or is false:
    ///     1. Append 0x66616c7365 (false) to result.
    /// 10. Otherwise:
    ///     1. Append 0x74727565 (true) to result.
    /// 11. Create a temporary copy of the CollectedClientData and remove the fields
    ///     type, challenge, origin, and crossOrigin (if present).
    /// 12. If no fields remain in the temporary copy then:
    ///     1. Append 0x7d (}) to result.
    /// 13. Otherwise:
    ///     1. Invoke serialize JSON to bytes on the temporary copy to produce a byte string remainder.
    ///         (see below for how this is done)
    ///     2. Append 0x2c (,) to result.
    ///     3. Remove the leading byte from remainder.
    ///     4. Append remainder to result.
    /// 14. The result of the serialization is the value of result.
    ///
    /// From step 13.1
    /// To serialize a JavaScript value to JSON bytes, given a JavaScript value value:
    ///     1. Let string be the result of serializing a JavaScript value to a JSON string given value.
    ///     2. Return the result of running UTF-8 encode on string.
    fn collected_client_data_to_json_bytes(ccd: &CollectedClientData) -> Vec<u8> {
        let mut result: Vec<u8> = Vec::new();

        // Append {"type":
        result.extend(b"{\"type\":");
        // Append type value
        result.extend(ccd_to_string(ccd.ty.to_string().as_str()));
        // Append ,"challenge":
        result.extend(b",\"challenge\":");
        // Append challenge value
        result.extend(ccd_to_string(ccd.challenge.to_string().as_str()));
        // Append ,"origin":
        result.extend(b",\"origin\":");
        // Append origin value
        result.extend(ccd_to_string(ccd.origin.as_str()));
        // Append ,"crossOrigin":
        result.extend(b",\"crossOrigin\":");

        if let Some(cross_origin) = ccd.cross_origin {
            if cross_origin {
                // Append true
                result.extend(b"true");
            } else {
                // Append false
                result.extend(b"false");
            }
        } else {
            // Append false if crossOrigin is not present
            result.extend(b"false");
        }

        // Create a temporary copy of CollectedClientData
        let temp_copy = ccd.clone();

        // Check if any fields other than type, challenge, origin, and crossOrigin remain in the temporary copy
        if temp_copy.unknown_keys.is_empty() {
            // If no fields remain, append }
            result.push(b'}');
        } else {
            // Otherwise, invoke serialize JSON to bytes on the temporary copy to produce a byte string remainder
            let remainder = serde_json::to_vec(&temp_copy.unknown_keys)
                .expect("Unable to serialize CollectedClientData to vector");

            // Append ,
            result.push(b',');

            // Remove the leading byte from remainder
            let mut remainder = remainder.into_iter();
            remainder.next();

            // Append remainder to result
            result.extend(remainder);
        }

        result
    }

    /// This helper function generates random bytes to simulate a raw transaction and challenge. Returns:
    /// 1. Fake, fixed byte raw txn, `t`
    /// 2. The challenge -> SHA3_256(t)
    ///
    /// For context, during a WebAuthn assertion, the device authenticator signs over the binary
    /// concatenation of `authenticatorData` and SHA256(`clientDataJSON`).
    ///
    /// `ClientDataJSON` contains a `challenge` field which is used to store the SHA3_256 of the `RawTransaction`.
    fn generate_random_challenge_data(
        sender_addr: AccountAddress,
    ) -> (RawTransaction, RawTxnSigningMessage, Challenge) {
        let raw_txn = get_test_raw_transaction(sender_addr, 0, None, None, None, None);

        // Generate signing message (returns the concatenation of hash prefix || BCS serialization of transaction)
        let raw_txn_signing_message =
            signing_message(&raw_txn).expect("Unexpected BCS serialization error");
        // then generates the SHA3-256 digest of signing message as the challenge
        let challenge = HashValue::sha3_256_of(raw_txn_signing_message.as_slice()).to_vec();

        (raw_txn, raw_txn_signing_message, challenge)
    }

    /// Randomly generates a sender for a Raw Transaction
    fn generate_sender() -> (PublicKey, AccountAddress) {
        let sender = PrivateKey::generate_for_testing();
        let sender_pub = sender.public_key();

        let single_sender_auth =
            AuthenticationKey::any_key(AnyPublicKey::secp256r1_ecdsa(sender_pub.clone()));
        let single_sender_addr = single_sender_auth.account_address();

        (sender_pub, single_sender_addr)
    }

    /// Converts from a DER encoded public key to [`Secp256r1PublicKey`](PublicKey)
    fn secp256r1_der_to_webauthn_pub_key(der_pub_key_bytes: Bytes) -> anyhow::Result<PublicKey> {
        let verifying_key =
            p256::ecdsa::VerifyingKey::from_public_key_der(der_pub_key_bytes.as_slice())
                .map_err(|e| anyhow!("{:?}", e))?;

        // To encoded point with no compression
        let encoded_string = hex::encode(verifying_key.to_encoded_point(false).as_bytes());
        PublicKey::from_encoded_string(encoded_string.as_str()).map_err(|e| anyhow!("{:?}", e))
    }

    /// Converts from a DER encoded signature to [`AssertionSignature`](AssertionSignature)
    fn secp256r1_der_to_signature(der_sig_bytes: Bytes) -> anyhow::Result<Signature> {
        let signature = p256::ecdsa::Signature::from_der(der_sig_bytes.as_slice())
            .map_err(|e| anyhow!("{:?}", e))?;
        let sig_bytes = signature.to_bytes();
        Signature::make_canonical_from_bytes_unchecked(sig_bytes.as_slice())
            .map_err(|e| anyhow!("{:?}", e))
    }

    /// Helper function that creates boilerplate for a WebAuthn registration (creation of a new passkey)
    async fn registration_helper(
        challenge: Challenge,
    ) -> anyhow::Result<(
        CreatedPublicKeyCredential,
        PublicKey,
        AuthenticatedPublicKeyCredential,
    )> {
        // Parse challenge provided by user
        let challenge_bytes = Bytes::from(challenge);

        // The user entity
        let user_entity = PublicKeyCredentialUserEntity {
            id: random_vec(32).into(),
            display_name: "Aptos Passkey".into(),
            name: "aptos@aptos.dev".into(),
        };
        let origin = Url::parse("http://localhost:4000")?;
        // First create an Authenticator for the Client to use.
        let my_aaguid = Aaguid::new_empty();
        let user_validation_method = MyUserValidationMethod {};
        // Create the CredentialStore for the Authenticator.
        // Option<Passkey> is the simplest possible implementation of CredentialStore
        let store: Option<Passkey> = None;
        let my_authenticator = Authenticator::new(my_aaguid, store, user_validation_method);

        // Create the Client, enable origins that are localhost based
        // If you are creating credentials, you need to declare the Client as mut
        let mut my_client = Client::new(my_authenticator).allows_insecure_localhost(true);

        let parameters_from_rp = PublicKeyCredentialParameters {
            ty: PublicKeyCredentialType::PublicKey,
            alg: coset::iana::Algorithm::ES256,
        };

        // The following values, provided as parameters to this function would usually be
        // retrieved from a Relying Party according to the context of the application.
        let request = CredentialCreationOptions {
            public_key: PublicKeyCredentialCreationOptions {
                rp: PublicKeyCredentialRpEntity {
                    id: None, // Leaving the ID as None means use the effective domain
                    name: origin.domain().unwrap().into(),
                },
                user: user_entity,
                challenge: challenge_bytes.clone(),
                pub_key_cred_params: vec![parameters_from_rp],
                timeout: None,
                exclude_credentials: None,
                authenticator_selection: None,
                attestation: AttestationConveyancePreference::None,
                attestation_formats: None,
                extensions: None,
                hints: None,
            },
        };

        // Now create the credential.
        let my_webauthn_credential = my_client
            .register(&origin, request, None)
            .await
            .map_err(|e| anyhow!("{:?}", e))?;

        // Now try and authenticate
        let credential_request = CredentialRequestOptions {
            public_key: PublicKeyCredentialRequestOptions {
                challenge: challenge_bytes,
                timeout: None,
                rp_id: Some(String::from(origin.domain().unwrap())),
                allow_credentials: None,
                user_verification: UserVerificationRequirement::default(),
                attestation: Default::default(),
                attestation_formats: None,
                extensions: None,
                hints: None,
            },
        };

        let authenticated_cred = my_client
            .authenticate(&origin, credential_request, None)
            .await
            .map_err(|e| anyhow!("{:?}", e))?;

        let secp256r1_public_key = secp256r1_der_to_webauthn_pub_key(
            my_webauthn_credential.response.public_key.clone().unwrap(),
        )?;

        Ok((
            my_webauthn_credential,
            secp256r1_public_key,
            authenticated_cred,
        ))
    }

    /// This uses the mock passkey registration and assertion helpers from `passkey-rs` to test for
    /// `PartialAuthenticatorAssertionResponse` verification.
    #[tokio::test]
    async fn verify_partial_authenticator_assertion_response() {
        let (.., sender_address) = generate_sender();
        let (raw_txn, _raw_txn_signing_message, challenge) =
            generate_random_challenge_data(sender_address);

        // Assert challenge is 32 bytes -> SHA3-256(hash prefix + BCS encoded raw txn)
        assert_eq!(challenge.len(), 32);

        let (.., p256_pub_key, auth_pub_key_cred) =
            registration_helper(challenge.clone()).await.unwrap();
        let any_public_key = AnyPublicKey::Secp256r1Ecdsa {
            public_key: p256_pub_key,
        };

        let webauthn_p256_signature =
            secp256r1_der_to_signature(auth_pub_key_cred.response.signature).unwrap();
        let canonical_webauthn_p256_signature = Signature::make_canonical(&webauthn_p256_signature);

        let webauthn_signature = AssertionSignature::Secp256r1Ecdsa {
            signature: canonical_webauthn_p256_signature,
        };
        let authenticator_data = auth_pub_key_cred
            .response
            .authenticator_data
            .as_slice()
            .to_vec();
        let client_data_json = auth_pub_key_cred
            .response
            .client_data_json
            .as_slice()
            .to_vec();

        // Partial Authenticator Assertion Response
        let paar = PartialAuthenticatorAssertionResponse::new(
            webauthn_signature,
            authenticator_data,
            client_data_json,
        );

        let verification = paar.verify(&raw_txn, &any_public_key);
        assert!(verification.is_ok());
    }

    fn parse_cose_key_from_att_obj(att_obj: &str) -> anyhow::Result<CoseKey> {
        let att_obj_bytes = Bytes::try_from(att_obj).map_err(|e| anyhow!("{:?}", e))?;

        // Decode CBOR encoded attestation object to get attestation object
        let att_obj: ctap2::make_credential::Response =
            ciborium::de::from_reader(att_obj_bytes.as_slice())
                .expect("could not deserialize response");

        let attested_credential_data = att_obj
            .auth_data
            .attested_credential_data
            .expect("does not contain attested credential data");

        Ok(attested_credential_data.key)
    }

    fn generate_secp256r1_public_key_from_cose_key(
        cose_key: &CoseKey,
    ) -> anyhow::Result<PublicKey> {
        let public_key_der =
            public_key_der_from_cose_key(cose_key).map_err(|e| anyhow!("{:?}", e))?;
        secp256r1_der_to_webauthn_pub_key(public_key_der)
    }

    /// This test uses a real WebAuthn [AuthenticatorAssertionResponse](passkey_types::webauthn::AuthenticatorAssertionResponse)
    /// from a passkey generated with the `passkeys-ts` test client.
    ///
    /// See <https://github.com/aptos-labs/passkeys-ts>
    #[tokio::test]
    async fn verify_real_partial_authenticator_assertion_response() {
        // Parse passkey credential registration response to get the public key
        let cose_key = parse_cose_key_from_att_obj(ATTESTATION_OBJECT).unwrap();
        let secp256r1_public_key = generate_secp256r1_public_key_from_cose_key(&cose_key).unwrap();
        let any_public_key = AnyPublicKey::Secp256r1Ecdsa {
            public_key: secp256r1_public_key,
        };

        let raw_txn: RawTransaction = bcs::from_bytes(RAW_TXN_BCS_BYTES).unwrap();
        let secp256r1_signature = Signature::try_from(SIGNATURE).unwrap();
        let paar = PartialAuthenticatorAssertionResponse::new(
            AssertionSignature::Secp256r1Ecdsa {
                signature: secp256r1_signature,
            },
            AUTHENTICATOR_DATA.to_vec(),
            CLIENT_DATA_JSON.to_vec(),
        );

        let verification_result = paar.verify(&raw_txn, &any_public_key);
        assert!(verification_result.is_ok());
    }

    /// Uses a SPC payload to test verification even when the clientDataJSON or `CollectedClientData`
    /// is extended
    ///
    /// See <https://w3c.github.io/secure-payment-confirmation/>
    #[tokio::test]
    async fn verify_real_partial_authenticator_assertion_response_from_spc() {
        // Parse passkey credential registration response to get the public key
        let cose_key = parse_cose_key_from_att_obj(SPC_ATTESTATION_OBJECT).unwrap();
        let secp256r1_public_key = generate_secp256r1_public_key_from_cose_key(&cose_key).unwrap();
        let any_public_key = AnyPublicKey::Secp256r1Ecdsa {
            public_key: secp256r1_public_key,
        };

        let raw_txn: RawTransaction = bcs::from_bytes(RAW_TXN_BCS_BYTES).unwrap();

        let authenticator_data: Vec<u8> = vec![
            73, 150, 13, 229, 136, 14, 140, 104, 116, 52, 23, 15, 100, 118, 96, 91, 143, 228, 174,
            185, 162, 134, 50, 199, 153, 92, 243, 186, 131, 29, 151, 99, 5, 0, 0, 0, 0,
        ];

        let client_data_json: Vec<u8> = vec![
            123, 34, 116, 121, 112, 101, 34, 58, 34, 112, 97, 121, 109, 101, 110, 116, 46, 103,
            101, 116, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110, 103, 101, 34, 58, 34, 101, 85,
            102, 49, 97, 88, 119, 100, 116, 72, 75, 110, 73, 89, 85, 88, 107, 84, 103, 72, 120,
            109, 87, 116, 89, 81, 95, 85, 48, 99, 51, 79, 56, 76, 100, 109, 120, 51, 80, 84, 65,
            95, 103, 34, 44, 34, 111, 114, 105, 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 58,
            47, 47, 108, 111, 99, 97, 108, 104, 111, 115, 116, 58, 53, 49, 55, 51, 34, 44, 34, 99,
            114, 111, 115, 115, 79, 114, 105, 103, 105, 110, 34, 58, 102, 97, 108, 115, 101, 44,
            34, 112, 97, 121, 109, 101, 110, 116, 34, 58, 123, 34, 114, 112, 73, 100, 34, 58, 34,
            108, 111, 99, 97, 108, 104, 111, 115, 116, 34, 44, 34, 116, 111, 112, 79, 114, 105,
            103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 58, 47, 47, 108, 111, 99, 97, 108, 104,
            111, 115, 116, 58, 53, 49, 55, 51, 34, 44, 34, 112, 97, 121, 101, 101, 79, 114, 105,
            103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47, 108, 111, 99, 97, 108,
            104, 111, 115, 116, 58, 52, 48, 48, 48, 34, 44, 34, 116, 111, 116, 97, 108, 34, 58,
            123, 34, 118, 97, 108, 117, 101, 34, 58, 34, 49, 46, 48, 49, 34, 44, 34, 99, 117, 114,
            114, 101, 110, 99, 121, 34, 58, 34, 65, 80, 84, 34, 125, 44, 34, 105, 110, 115, 116,
            114, 117, 109, 101, 110, 116, 34, 58, 123, 34, 105, 99, 111, 110, 34, 58, 34, 104, 116,
            116, 112, 115, 58, 47, 47, 97, 112, 116, 111, 115, 108, 97, 98, 115, 46, 99, 111, 109,
            47, 97, 115, 115, 101, 116, 115, 47, 102, 97, 118, 105, 99, 111, 110, 45, 50, 99, 57,
            101, 50, 51, 97, 98, 99, 51, 97, 51, 102, 52, 99, 52, 53, 48, 51, 56, 101, 56, 99, 55,
            56, 52, 98, 48, 97, 52, 101, 99, 98, 57, 48, 53, 49, 98, 97, 97, 46, 105, 99, 111, 34,
            44, 34, 100, 105, 115, 112, 108, 97, 121, 78, 97, 109, 101, 34, 58, 34, 80, 101, 116,
            114, 97, 32, 116, 101, 115, 116, 34, 125, 125, 44, 34, 111, 116, 104, 101, 114, 95,
            107, 101, 121, 115, 95, 99, 97, 110, 95, 98, 101, 95, 97, 100, 100, 101, 100, 95, 104,
            101, 114, 101, 34, 58, 34, 100, 111, 32, 110, 111, 116, 32, 99, 111, 109, 112, 97, 114,
            101, 32, 99, 108, 105, 101, 110, 116, 68, 97, 116, 97, 74, 83, 79, 78, 32, 97, 103, 97,
            105, 110, 115, 116, 32, 97, 32, 116, 101, 109, 112, 108, 97, 116, 101, 46, 32, 83, 101,
            101, 32, 104, 116, 116, 112, 115, 58, 47, 47, 103, 111, 111, 46, 103, 108, 47, 121, 97,
            98, 80, 101, 120, 34, 125,
        ];

        let collected_client_data_string = r#"
            {
              "type": "payment.get",
              "challenge": "eUf1aXwdtHKnIYUXkTgHxmWtYQ_U0c3O8Ldmx3PTA_g",
              "origin": "http://localhost:5173",
              "crossOrigin": false,
              "payment": {
                "rpId": "localhost",
                "topOrigin": "http://localhost:5173",
                "payeeOrigin": "https://localhost:4000",
                "total": {
                  "value": "1.01",
                  "currency": "APT"
                },
                "instrument": {
                  "icon": "https://aptoslabs.com/assets/favicon-2c9e23abc3a3f4c45038e8c784b0a4ecb9051baa.ico",
                  "displayName": "Petra test"
                }
              },
              "other_keys_can_be_added_here": "do not compare clientDataJSON against a template. See https://goo.gl/yabPex"
            }"#;

        let collected_client_data: CollectedClientData =
            serde_json::from_str(collected_client_data_string).unwrap();

        // Ensure the byte serialization is correct
        assert_eq!(
            collected_client_data_to_json_bytes(&collected_client_data),
            client_data_json
        );

        let signature: Vec<u8> = vec![
            254, 40, 71, 181, 216, 187, 97, 118, 196, 106, 251, 170, 106, 47, 184, 77, 174, 187,
            18, 135, 14, 184, 149, 146, 37, 80, 10, 37, 137, 187, 68, 84, 43, 29, 246, 120, 32, 23,
            254, 69, 228, 43, 148, 122, 244, 216, 183, 80, 139, 56, 12, 62, 195, 49, 97, 184, 185,
            170, 184, 138, 123, 39, 106, 237,
        ];
        let secp256r1_signature = Signature::try_from(signature.as_slice()).unwrap();

        let paar = PartialAuthenticatorAssertionResponse::new(
            AssertionSignature::Secp256r1Ecdsa {
                signature: secp256r1_signature,
            },
            authenticator_data,
            client_data_json,
        );

        let verification_result = paar.verify(&raw_txn, &any_public_key);
        assert!(verification_result.is_ok());
    }

    /// Checks that verification for `PartialAuthenticatorAssertionResponse` fails when
    /// 1. `client_data_json` is empty
    /// 2. `authenticator_data` is empty
    /// 3. Raw Transaction is incorrect (does not match challenge)
    /// 4. Public Key is incorrect
    #[test]
    fn verify_real_partial_authenticator_assertion_response_failure() {
        // Parse passkey credential registration response to get the public key
        let cose_key = parse_cose_key_from_att_obj(ATTESTATION_OBJECT).unwrap();
        let secp256r1_public_key = generate_secp256r1_public_key_from_cose_key(&cose_key).unwrap();
        let any_public_key = AnyPublicKey::Secp256r1Ecdsa {
            public_key: secp256r1_public_key,
        };

        let raw_txn: RawTransaction = bcs::from_bytes(RAW_TXN_BCS_BYTES).unwrap();

        let secp256r1_signature = Signature::try_from(SIGNATURE).unwrap();

        // Empty client_data_json
        let bad_paar = PartialAuthenticatorAssertionResponse::new(
            AssertionSignature::Secp256r1Ecdsa {
                signature: secp256r1_signature.clone(),
            },
            AUTHENTICATOR_DATA.to_vec(),
            vec![],
        );
        let verification_result = bad_paar.verify(&raw_txn, &any_public_key);
        assert!(verification_result.is_err());

        // Empty authenticator_data
        let bad_paar = PartialAuthenticatorAssertionResponse::new(
            AssertionSignature::Secp256r1Ecdsa {
                signature: secp256r1_signature.clone(),
            },
            vec![],
            CLIENT_DATA_JSON.to_vec(),
        );
        let verification_result = bad_paar.verify(&raw_txn, &any_public_key);
        assert!(verification_result.is_err());

        // Incorrect raw transaction
        let (.., sender_address) = generate_sender();
        let bad_raw_txn = get_test_raw_transaction(sender_address, 0, None, None, None, None);
        let bad_paar = PartialAuthenticatorAssertionResponse::new(
            AssertionSignature::Secp256r1Ecdsa {
                signature: secp256r1_signature.clone(),
            },
            AUTHENTICATOR_DATA.to_vec(),
            CLIENT_DATA_JSON.to_vec(),
        );
        let verification_result = bad_paar.verify(&bad_raw_txn, &any_public_key);
        assert!(verification_result.is_err());

        // Incorrect public key
        let paar = PartialAuthenticatorAssertionResponse::new(
            AssertionSignature::Secp256r1Ecdsa {
                signature: secp256r1_signature,
            },
            AUTHENTICATOR_DATA.to_vec(),
            CLIENT_DATA_JSON.to_vec(),
        );
        let mut rng: StdRng = SeedableRng::from_seed([0; 32]);
        let bad_private_key: secp256r1_ecdsa::PrivateKey =
            aptos_crypto::Uniform::generate(&mut rng);
        let bad_public_key = PrivateKey::public_key(&bad_private_key);
        let bad_any_public_key = AnyPublicKey::Secp256r1Ecdsa {
            public_key: bad_public_key,
        };
        let verification_result = paar.verify(&raw_txn, &bad_any_public_key);
        assert!(verification_result.is_err());

        // Correct
        let verification_result = paar.verify(&raw_txn, &any_public_key);
        assert!(verification_result.is_ok());
    }
}
